from datetime import datetime
from enum import Enum
from logging import Logger
from typing import Any

from util.dateutil import ts_str, date_str
from x_pc.core.resource_config import ResourceType
from x_pc.db.command.model import CommandRouteStep, CommandRoute
from x_pc.db.common.model import WorkType


class TaskSource(Enum):
    """
    任务来源 发货、在库、预作业、理货
    """
    # 发货任务
    DELIVERY = 'deliver'
    # 在库任务
    INTERIOR = 'interior'
    # 预作业任务
    PREWORK = 'prework'
    # 理货任务
    TALLY = 'tally'


class CommandTypeCode(Enum):
    """
    作业类型
    """
    # 发货任务
    PICK_INTEGRATE = "PICK_INTEGRATE"
    # 件箱盘点
    INV_CHECK = "INV_CHECK"
    # 转编码
    SKU_TRF = "SKU_TRF"
    # IQC_CALL
    IQC_CALL = "IQC_CALL"
    # OVER_IQC_CALL
    OVER_IQC_CALL = "OVER_IQC_CALL"
    # REP_TASK_SPLIT
    REP_TASK_SPLIT = 'REP_TASK_SPLIT'
    # REP_TASK_ZBS
    REP_TASK_ZBS = 'REP_TASK_ZBS'


class InOutMode(Enum):
    """
    出入口模式
    """
    # 主入
    IN = 1
    # 主出
    OUT = 2
    # 既入又出
    INOUT = 3


class Direction(Enum):
    """
    货物方向
    """
    LEFT = 1
    RIGHT = 2
    # 默认值
    NONE = 0


class Loc(object):
    # 位置长度 固定为13
    LENGTH = 13
    # 原始数据
    raw_loc: str = None

    # 巷道
    lane: str = None
    # 方向 左侧、右侧
    direction: Direction = None
    # 列
    column: str = None
    # 层
    layer: str = None
    # 深度
    depth: str = None

    def __init__(self, loc_str: str = None):
        self.raw_loc = loc_str
        if not loc_str or len(loc_str) != Loc.LENGTH:
            return
        self.lane, self.direction, self.column, self.layer, self.depth = Loc.parse(loc_str)

    @staticmethod
    def parse(loc_str: str) -> tuple[str, Direction, str, str, str] | None:
        """
        简单解析
        - 101 1 082 004 001
        - 101巷道左侧082列004层深度001
        :param loc_str:
        :return:
        """
        if not loc_str or len(loc_str) != Loc.LENGTH:
            return None
        return loc_str[:3], Direction(int(loc_str[3:4])), loc_str[4:7], loc_str[7:10], loc_str[10:13]

    def __str__(self):
        return f'{self.__class__.__name__}({self.raw_loc})'


def pos_str(pos: Any) -> str:
    return pos if isinstance(pos, str) else str(pos)


class PackType(Enum):
    # 栈板
    ZB = 1
    # 纸箱
    ZX = 2
    # 周转箱
    ZZX = 3
    # 箱式 (纸箱、周转箱)
    XS = 4
    # MPQ
    MPQ = 5


class WorkTypeEnum(Enum):
    """
    设备工作类型
    """
    # FHXSCK 发货箱式出库 (出货口)
    FHXSCK = 2004
    # ZKXSCK 在库箱式出库 (出货口)
    ZKXSCK = 2005

    # GTP作业 (会生成新LPN)
    GTP = 3012
    # ZBJL 栈板拣料作业 (会生成新LPN)
    ZBJL = 3014


class DeviceType(Enum):
    """
    设备类型
    """
    # ------------- Unit
    # 发EMS/ODM出货口
    EMSC = 205
    # 负一楼自制发货口
    ZZC = 206
    # IQCC 二楼IQC出货口
    IQCC = 208
    # IQCCR 出入货口设备组
    IQCCR = 210
    # ------------- Center
    # GTP设备
    GTP = 306
    # ZCB 综合换包站台
    ZCB = 304
    # ZHB 专用换包站台
    ZHB = 305
    # ZHC 综合拆板站台
    ZHC = 303
    # ZBJL 栈板拣料工作台
    ZBJL = 309


class QueueState(Enum):
    """
    母任务 状态
    工单状态
    1 新建、 2:已排程、3、已下发、4已完成、5已取消
    """
    NEW = 1
    PLAN = 2
    ISSUE = 3
    ISSUE2 = 11
    ASSIGNING = 13
    COMPLETE = 4
    CANCEL = 5


class TaskStatus(Enum):
    """
    任务状态
    """
    INIT = 1
    RUNNING = 2
    DONE = 3
    ERROR = 4


class TaskProcess(object):
    """
    任务具体处理流程
    """
    process_id: str = None
    task_id: str = None
    # 业务类关键数据
    order: int = None
    device_type: str = None
    device_code: str = None
    # 开始、结束 位置
    begin_pos: str = None
    end_pos: str = None
    # 开始、结束 时间
    begin_ts: float = None
    end_ts: float = None
    # 开始、结束 包装类型
    origin_pack_type: int = None
    target_pack_type: int = None
    # 开始、结束 PCS数量
    origin_sku_num: int = None
    target_sku_num: int = None
    # 操作包装 类型及数量
    operate_pack_type: int = None
    operate_pack_num: int = None
    # 资源ID
    resource_id: str = None

    # 附加记录
    ext_target_lpn: str = None

    # 附加属性
    ext_step: CommandRouteStep = None
    ext_resource_type: ResourceType = None
    ext_work_type: WorkType = None

    def __init__(self, process_id: str | None, task_id: str | None, order: int, device_type: str | None,
                 device_code: str | None, begin_pos: str | None):
        self.process_id = process_id
        self.task_id = task_id
        self.order = order
        self.device_type = device_type
        self.device_code = device_code
        self.begin_pos = begin_pos

    def set_origin_pack_type(self, pack_type: int):
        self.origin_pack_type = pack_type
        self.target_pack_type = pack_type

    def set_origin_sku_num(self, sku_num: int):
        self.origin_sku_num = sku_num
        self.target_sku_num = sku_num

    def to_dict(self):
        """
        模型转Dict 用于生成JSON
        :return:
        """
        return {'processId': self.process_id, 'taskId': self.task_id, 'order': self.order,
                'deviceType': self.device_type, 'deviceCode': self.device_code,
                'locationStart': pos_str(self.begin_pos), 'locationEnd': pos_str(self.end_pos),
                'startTime': ts_str(self.begin_ts), 'endTime': ts_str(self.end_ts)}

    def __str__(self):
        return (f'<{self.__class__.__name__}> '
                f'order: {self.order}'
                f'\tdevice_type: {self.device_type}, device_code: {self.device_code}'
                f'\tpos: begin_pos: {self.begin_pos}, end_pos: {self.end_pos}'
                f'\tts: begin_ts: {ts_str(self.begin_ts)}, end_ts: {ts_str(self.end_ts)}')


class Task(object):
    """
    任务
    """
    # 附加属性
    ext_source: TaskSource = None
    ext_route: CommandRoute = None
    # 无target_pack_type多任务场景，存储第二条路径
    ext_route_2: CommandRoute = None
    # 无target_pack_type多任务场景，存储第二条处理线路
    ext_process_list_2: list[TaskProcess] = []
    # 最小拣选单元包含PCS数, 纸箱包含PCS数, 栈板包含PCS数
    ext_innerpack, ext_casecant, ext_act_num = None, None, None
    # 存放子任务信息
    ext_sub_task_list = []
    # 存放父任务信息
    ext_parent_task = None
    # 在库 指定设备相对应的 设备类别(equ_type)
    ext_zk_equ_type: int = None
    # 多DC场景，GroupID
    ext_group_id = None
    ext_group_tasks: list = []
    ext_group_routes: list = []
    ext_factory_group_id = None
    # 标记PTX任务
    ext_ptx = False
    # PTX任务执行时间
    ext_ptx_time: float = 0
    # ext last err? info
    ext_last_info: str = None
    # 是否做过扣减PCS (出货口分配)
    ext_costed_pcs: bool = False
    # 强制指定执行时间
    ext_reassign_time: float = 0
    # 是否强制GTP
    ext_force_gtp: bool = False
    # 是否是实发实
    ext_delivery_real: bool = False
    # 指定出货口 (指定出货口Codes + 设备Type)  [非当前楼层]
    ext_designate_outlets: list[str] = []
    ext_designate_equtype: int = None
    #
    task_id: str = None
    plan_start_time: datetime = None
    plan_end_time: datetime = None
    front_task: str = None
    start_time: float = None
    end_time: float = None
    status: int = None
    lpn: str = None  # 作业LPN
    sku: str = None

    # -------- 在库 出货口分配使用
    # 单子的编号字段 (标识哪些Task归属同一个单子)
    cwmsinvtaskdetil: str = None
    # 任务指定 具体的出口 设备编号 equ_code (若为空，则为不指定)
    consigneekey: str = None

    # 交付对象编码
    deliver_factory_code: str = None
    # 发车时间
    vehicle_task_begin_time: datetime = None
    # 时间窗开始时间
    time_window_start_time: datetime = None
    # 开始、结束 位置
    begin_pos: Loc = None
    end_pos: Any = None
    # 开始、结束 包装类型
    origin_pack_type: int = None
    target_pack_type: int = None
    # 开始、结束 PCS数量
    origin_sku_num: int = None
    target_sku_num: int = None
    # 任务处理子路径
    process_list: list[TaskProcess] = []

    # 状态 (辅助字段)
    _run_status: TaskStatus = TaskStatus.INIT

    def __init__(self, task_id: str, plan_start_time: datetime | None, plan_end_time: datetime | None,
                 front_task: str | None, status: int | None, vehicle_task_begin_time: datetime,
                 time_window_start_time: datetime, wops_vehicle_taskid: str, begin_pos: str,
                 process: list[TaskProcess]):
        self.task_id = task_id
        self.plan_start_time = plan_start_time
        self.plan_end_time = plan_end_time
        self.status = status
        self.vehicle_task_begin_time = vehicle_task_begin_time
        self.time_window_start_time = time_window_start_time
        self.wops_vehicle_taskid = wops_vehicle_taskid
        self.front_task = front_task
        self.begin_pos = Loc(begin_pos)
        self.process_list = process
        # 子任务数据
        self.ext_sub_task_list = []
        #
        self.ext_designate_outlets: list[str] = []

    def pre_process(self, logger: Logger = None) -> TaskProcess | None:
        """
        待执行流程 的 前序流程
        :return: 
        """
        if not self.process and logger:
            logger.warning('[PC] NO PROCESS ERROR. task: %s, process_list: %s', self.task_id, self.process_list)
        idx = self.process.order
        if idx == 1:
            return None
        return self.process_list[idx - 2]

    def next_process(self) -> TaskProcess | None:
        """
        待执行流程 的 后置流程
        :return:
        """
        idx = self.process.order
        idx_max = len(self.process_list)
        if idx == idx_max:
            return None
        return self.process_list[idx + 1]

    @property
    def process(self) -> TaskProcess | None:
        """
        待执行流程 (辅助字段)
        :return:
        """
        init_process_list = sorted([_ for _ in self.process_list if not _.end_ts], key=lambda _: _.order)
        return init_process_list[0] if init_process_list else None

    def factory_code(self):
        """
        交付对象标识 (急料 则添加前缀 Urgent)
        :return:
        """
        if self.is_urgent:
            return f'urgent_{self.deliver_factory_code}'
        return self.deliver_factory_code

    def common_cost(self):
        return self.end_time - self.start_time

    def cd_time(self) -> None | tuple[float, float]:
        """
        穿梭车/堆垛机 工作时间
        """
        if self.run_status == TaskStatus.DONE:
            ddj_process = self.process_list[0] if self.process_list else None
            if ddj_process and ddj_process.begin_ts and ddj_process.end_ts:
                return ddj_process.begin_ts, ddj_process.end_ts
        return None

    def cd_cost(self) -> None | float:
        """
        穿梭车/堆垛机 操作耗时
        """
        ddj_time = self.cd_time()
        if ddj_time:
            return ddj_time[1] - ddj_time[0]
        return None

    @property
    def is_urgent(self) -> bool:
        """
        是否为 急货
        """
        return self.wops_vehicle_taskid and self.wops_vehicle_taskid.upper() == 'URGENT'

    @property
    def is_master_dc_task(self) -> bool | None:
        """
        是否为多DC场景，主任务(ext_group_tasks的第一个)
        """
        if not self.ext_group_id:
            return None
        return self.task_id == self.ext_group_tasks[0].task_id

    @property
    def run_status(self):
        return self._run_status

    @run_status.setter
    def run_status(self, data):
        self._run_status = data

    def set_run_status(self, status: TaskStatus, sys_data):
        if status == TaskStatus.ERROR:
            from x_pc.core.core import WareHouse
            from x_pc.core.system_context import SysDataCtx

            sys_data: SysDataCtx
            ware_house: WareHouse = sys_data.ware_house
            #
            sys_data.logger.warning('[PC] task: %s 任务异常, 尝试修正出货口占用', self.task_id)
            ware_house.system_unit.fix_reassign_port_when_error(self)
        self._run_status = status

    def to_dict(self):
        return {'taskId': self.task_id, 'planStartTime': date_str(self.plan_start_time),
                'planEndTime': date_str(self.plan_end_time), 'frontTask': self.front_task,
                'startTime': ts_str(self.start_time), 'endTime': ts_str(self.end_time),
                'locationStart': pos_str(self.begin_pos), 'locationEnd': pos_str(self.end_pos), 'status': self.status,
                'processList': [_.to_dict() for _ in self.process_list]}

    def __str__(self):
        return (f'<{self.__class__.__name__}>'
                f'\n\ttask_id: {self.task_id}'
                f'\n\trun_status: {self.run_status}'
                f'\n\tbegin: {self.begin_pos.raw_loc}'
                f'\n\tend: {self.end_pos}'
                f'\n\tprocess:\n' + "\n".join([f"\t\t{_}" for _ in self.process_list]))


class TaskCtx(object):
    """
    任务Context
    """
    # 任务
    task: Task = None

    # 栈板 上下口
    zbsx = None
    # 栈板 出入口
    zbcr = None

    # 件箱 层接口
    jxcj = None
    # 件箱 上下口
    jxsx = None

    def __init__(self, task: Task, zbsx=None, zbcr=None, jxcj=None, jxsx=None):
        self.task = task
        if zbsx:
            self.zbsx = zbsx
        if zbcr:
            self.zbcr = zbcr
        if jxcj:
            self.jxcj = jxcj
        if jxsx:
            self.jxsx = jxsx

    def __str__(self):
        return f'{self.__class__.__name__}(taskId: {self.task.task_id})'


def check_fix_task(task: Task):
    """
    校验&修正 任务
    :param task:
    :return:
    """
    process_list = task.process_list
    process_list.sort(key=lambda _: _.order)
    first_process = process_list[0]
    last_process = process_list[-1]
    # 最后一个过程处理完，且任务为运行状态
    if last_process.end_ts and task.run_status == TaskStatus.RUNNING:
        task.run_status = TaskStatus.DONE
    if task.run_status == TaskStatus.DONE:
        task.start_time = first_process.begin_ts
        task.end_pos = last_process.end_pos
        task.end_time = last_process.end_ts
    # 当有父任务时
    if task.ext_parent_task:
        parent_task: Task = task.ext_parent_task
        undone_tasks = [_ for _ in parent_task.ext_sub_task_list if _.run_status != TaskStatus.DONE]
        # 子任务全部处理完毕，设置父任务为完成状态
        if not undone_tasks:
            parent_task.run_status = TaskStatus.DONE
        if parent_task.run_status == TaskStatus.DONE:
            # 开始时间
            parent_task.process_list.sort(key=lambda _: _.order)
            first_process = parent_task.process_list[0]
            parent_task.start_time = first_process.begin_ts
            # 结束时间
            for task in parent_task.ext_sub_task_list:
                task.process_list.sort(key=lambda _: _.order)
            last_process = sorted([_.process_list[-1] for _ in parent_task.ext_sub_task_list], key=lambda _: _.end_ts)[
                -1]
            parent_task.end_pos = last_process.end_pos
            parent_task.end_time = last_process.end_ts
